This project documents the process of building a secure DevSecOps CI/CD pipeline from the ground up. The goal is to simulate how modern engineering teams integrate security directly into the software development lifecycle rather than treating it as a final checkpoint.
The project starts with foundational concepts such as Linux security practices, Python scripting, and file permission analysis. These fundamentals form the base for automating tasks and understanding system-level security controls.
As the project progresses, a small web application will be developed and containerized using Docker. The pipeline will then integrate automated security testing tools such as Static Application Security Testing (SAST), Dynamic Application Security Testing (DAST), and container vulnerability scanning. The objective is to demonstrate how security checks can run automatically within a CI/CD workflow.
Automation will be implemented using GitHub Actions to create a pipeline that builds the application, runs security scans, and enforces security checks before deployment. The project will also explore secure deployment strategies in a cloud environment.
This repository will evolve over time. Each phase of the project will introduce new security tools, automation workflows, and defensive practices commonly used in real DevSecOps environments. By the end of the project, it will represent a complete example of a secure development pipeline that integrates application development, infrastructure automation, and security testing.
Linux security fundamentals
Python scripting basics
File permission analysis and secure configuration practices
Planned Roadmap
Develop a simple web application
Containerize the application using Docker
Build a CI/CD pipeline using GitHub Actions
Integrate SAST security scanning
Integrate DAST testing
Implement container vulnerability scanning
Deploy the pipeline and application to a cloud environment
Create a fully functional secure DevSecOps pipeline that demonstrates practical security automation, suitable for portfolio demonstration and technical interviews.
During early testing of the application, several security weaknesses were identified. These findings are documented as part of the process of improving the security posture of the project while building the DevSecOps pipeline.
- No Brute Force Protection
The login mechanism currently allows unlimited authentication attempts without any restriction.
An attacker could repeatedly attempt different passwords until the correct one is discovered.
Implement login rate limiting
Introduce account lockout after multiple failed attempts
Add CAPTCHA or multi-factor authentication
- Information Disclosure
The login system currently returns a single generic message, “Credentials mismatch,” whenever authentication fails.
Although this behavior is secure today, problems could arise if future updates introduce different error messages for invalid usernames and incorrect passwords. Distinct responses could allow attackers to identify valid usernames and focus password-guessing attacks on those accounts.
Always return a single generic authentication failure message
Avoid revealing whether the username or password was incorrect
Maintain consistent error handling for all authentication failures
- Lack of Input Validation
User input fields currently accept any data without validation or restrictions.
Although the application currently does not use a database, unvalidated input could lead to security issues later if additional features are added (such as database queries or command execution).
Implement input validation for username and password fields
Restrict unexpected characters and enforce length limits
Sanitize all user input before processing
Previously, the application stored login credentials directly inside the source code. This is considered an insecure design because sensitive information should never be hardcoded in source files that may be pushed to version control systems like GitHub.
The credentials were removed from the source code and are now stored in environment variables.
The application now reads credentials using environment variables such as:
- USERNAME
- PASSWORD
- Prevents sensitive data from being exposed in the code repository
- Allows different credentials for different environments
- Follows security best practices used in DevSecOps workflows
- Protects secrets when sharing or publishing code
Environment variables are stored locally in a .env file which is ignored by Git.
- Broken Access Control
The application currently allows users to directly access protected routes such as /home without verifying whether the user is authenticated.
An attacker can bypass the login mechanism and gain unauthorized access to restricted pages and application functionality.
Implement session-based authentication
Restrict access to protected routes using authentication checks
Enforce role-based access control where required
- Cryptographic Failures
The application is currently running over HTTP, meaning that sensitive data such as usernames and passwords are transmitted in plain text without encryption.
An attacker on the same network could intercept login requests and capture sensitive credentials.
Enforce HTTPS using TLS certificates
Encrypt sensitive data in transit
Use secure password storage mechanisms such as hashing (bcrypt)
- Security Misconfiguration
The application is currently running with debug=True, which exposes detailed error messages and internal application information.
Note: host='0.0.0.0' is intentionally configured to enable Docker container networking. This is a known accepted risk and will be restricted in production deployment.
Attackers can view internal file paths, application logic, and potentially sensitive system details when errors occur.
Disable debug mode in production environments
Use secure configuration settings for deployment
Separate development and production configurations
- Security Logging and Monitoring Failures
The application does not log critical security events such as login attempts, IP addresses, timestamps, or authentication success/failure.
Attacks such as brute force attempts may go undetected, and there is no audit trail for investigating security incidents.
Implement structured logging for authentication events
Log IP address, timestamp, username, and result of login attempts
Integrate monitoring tools or SIEM systems for alerting
- Vulnerable and Outdated Components
The application depends on external libraries such as Flask. If these components are not regularly updated, they may contain known vulnerabilities.
Attackers can exploit publicly known vulnerabilities in outdated dependencies to compromise the application.
Regularly update dependencies to the latest secure versions
Integrate dependency scanning tools in the CI/CD pipeline
Monitor security advisories for used libraries
- Software and Data Integrity Failures
The application currently relies on external packages without verifying their integrity during installation.
If a malicious or tampered package is installed, it could introduce backdoors or malicious code into the application.
Use trusted package sources and verify package integrity using hashes
Implement dependency verification mechanisms
Use signed packages where possible
- Server-Side Request Forgery (SSRF)
Although not currently present in the application, SSRF could occur if future features allow the server to fetch external URLs based on user input.
An attacker could manipulate the server to make requests to internal systems or external malicious endpoints, potentially exposing sensitive internal data.
Validate and sanitize all user-supplied URLs
Restrict outbound network requests from the server
Use allowlists for permitted external requests
Automated pipeline includes pytest, Bandit SAST, and Safety dependency scanning.
The application does not implement a Content Security Policy (CSP) header, which means the browser does not have restrictions on executing scripts.
This vulnerability was identified using Dynamic Application Security Testing (DAST) with OWASP ZAP, which simulated real HTTP requests against the running application and analyzed the responses.
If an attacker is able to inject malicious JavaScript into the application (for example through input fields), the browser will execute it. This could lead to session hijacking, data theft, or unauthorized actions performed on behalf of the user.
Implement a Content Security Policy (CSP) header to restrict script execution sources
Sanitize and validate all user inputs to prevent script injection
Use secure frameworks or libraries that automatically escape output
Set HTTP security headers such as: Content-Security-Policy X-Content-Type-Options X-Frame-Options
##Container Vulnerability — CVE-2026-23949 (Identified by Trivy)
The Docker image used in this project contains a known vulnerability (CVE-2026-23949) within the base image or its underlying system packages.
This vulnerability was identified using Trivy, a container vulnerability scanner, as part of the CI/CD pipeline. Unlike Bandit (SAST), Safety (dependency scanning), and ZAP (DAST), Trivy scans the entire Docker image including OS packages, system libraries, and base image components.
CVE-2026-23949 is a path traversal vulnerability in jaraco.context that allows attackers to extract malicious tar archives outside the intended directory, potentially overwriting sensitive system files
If a vulnerable system package inside the container is exploited, an attacker could potentially gain unauthorized access or execute arbitrary code within the container environment, compromising the application and underlying system.
Use a minimal and secure base image (e.g., python:3.11-slim instead of full image)
Regularly update base images to patched versions
Integrate automated container scanning in CI/CD (Trivy already implemented)
Apply image hardening and remove unnecessary packages
Continuously monitor CVEs affecting base images and dependencies
###Cloud Deployment – AWS EC2
The application was deployed to a cloud environment using an AWS EC2 instance. The deployment demonstrates how a containerized application can be built, transferred, and executed in a remote server environment.
EC2 Instance Details Operating System: Ubuntu 24.04 LTS Instance Type: t3.micro (Free Tier eligible) Region: ap-southeast-2 (Sydney) Access Method: SSH using key-pair authentication
The instance was accessed securely using a private .pem key and configured via terminal.
###Security Group Configuration
A Security Group was configured to control inbound traffic to the EC2 instance.
Configured rules:
Port 22 (SSH) — Remote administrative access Port 5000 (Custom TCP) — Flask application access
Both ports were temporarily set to allow traffic from 0.0.0.0/0 to enable remote access and public testing.
#Risk
Allowing unrestricted access (0.0.0.0/0) increases exposure to unauthorized access attempts and brute-force attacks, especially on SSH.
#Future Mitigation
Restrict SSH access to specific trusted IP addresses Limit application exposure where possible Use secure access methods such as VPN or bastion host
###Running the Application in Docker
The application was deployed using Docker to ensure consistency between local, pipeline, and cloud environments.
Steps performed:
Cloned repository from GitHub Built Docker image on EC2 instance Ran container with port mapping and environment variables
Command used:
sudo docker run -d -p 5000:5000
-e username=admin
-e password=admin123
dockerimage
The application is accessible via:
http://:5000/login
##Known Risks
##Debug Mode Enabled
The Flask application is currently running with debug mode enabled.
#Risk
Debug mode can expose internal application details and may lead to information disclosure or arbitrary code execution.
#Future Mitigation Disable debug mode in production Use environment-based configuration
##Hardcoded Credentials in Docker Runtime
Credentials are passed directly in the Docker run command.
Risk
Sensitive data may be exposed through command history or system processes.
Future Mitigation Use secure secret management (e.g., AWS Secrets Manager) Store credentials in protected environment files Avoid passing secrets via command line